package WebDep;

#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

use strict;
use warnings;
use WWW::Curl::Easy;
use Digest::MD5 qw{md5 md5_hex md5_base64};
use File::Path;
use File::Basename qw{dirname};
use IO::Uncompress::Unzip qw{$UnzipError};
use Data::Dumper;
use Cwd qw{getcwd abs_path};

# columns from web_deps.txt in order
my @columns = qw{name version cdn_location compression filename source_dir final_dir};

# class method returns list of WebDep objects loaded from file
sub getDeps {
    my $class = shift;

    my $webdeps_file = shift;
    my @deps;
    open my $fh, '<', $webdeps_file or die "Can't open $webdeps_file\n";

    # final_dir within webdeps_file are absolute or relative to directory that file is in
    my $oldcwd      = getcwd();
    my $webdeps_dir = dirname($webdeps_file);
    chdir($webdeps_dir);
    while (<$fh>) {

        # comments only if line starts with #
        next if /^#/;
        chomp;
        my @parts = split( /\s*,\s*/, $_ );
        my $obj = bless {}, $class;
        for my $attrib (@columns) {
            $obj->{$attrib} = shift @parts;
        }
        $obj->{filename} =~ s{^/}{};
        $obj->{source_dir} =~ s{/$}{};
        $obj->{source_dir} =~ s{/$}{};

        my $final_dir = $obj->{final_dir};
        if ( !-d $final_dir ) {
            print "Making dir: $final_dir\n";
            mkpath($final_dir);
        }
        $obj->{final_dir} = abs_path($final_dir);
        push( @deps, $obj );
    }
    chdir($oldcwd);
    return @deps;
}

sub getSrcFileName {
    my $self = shift;
    my @parts;
    push @parts, $self->{source_dir} if $self->{source_dir} ne '';
    push @parts, $self->{filename};
    return join( '/', @parts );
}

sub getDestFileName {
    my $self = shift;
    return join( '/', $self->{final_dir}, $self->{filename} );
}

sub getDownloadContent {
    my $self = shift;
    my ( $response_body, $err ) = _getDownloadContent( $self->{cdn_location} );
    return ( $response_body, $err );
}

sub getContent {
    my $self = shift;
    my ( $content, $err ) = $self->getDownloadContent();
    if ( defined $err ) {
        die "$err\n";
    }
    my $srcfn = $self->getSrcFileName();
    if ( !exists $self->{content} ) {
        if ( $self->{compression} eq 'zip' ) {

            #print "Unzipping $srcfn\n";
            my $u = IO::Uncompress::Unzip->new( \$content ) or die "IO::Uncompress::Unzip failed: $UnzipError\n";
            my $found;
            while ( $u->nextStream() > 0 && !$u->eof() ) {
                my $name = $u->getHeaderInfo()->{Name};
                if ( $name eq $srcfn ) {
                    $found = $name;
                    last;
                }
            }
            if ( !defined $found ) {
                die "$srcfn not found in " . $self->{cdn_location} . "\n";
            }

            undef $/;    # slurp mode
            $content = <$u>;
            $u->close();
        }

        $self->{content} = $content;
    }
    return $self->{content};
}

sub needsUpdating {
    my $self = shift;
    my $fn   = $self->getDestFileName();

    # checksum and compare
    open my $fh, '<', $fn or die "Can't open existing file: $fn\n";
    my $md5_existing = md5_hex(<$fh>);
    close $fh;

    my $md5_new       = md5_hex( $self->getContent() );
    my $needsUpdating = ( $md5_new ne $md5_existing );
    return $needsUpdating;
}

sub update {
    my $self = shift;
    my $err;
    my $srcfn  = $self->getSrcFileName();
    my $destfn = $self->getDestFileName();
    my $action = "";

    # download archive
    if ( -f $destfn ) {
        if ( !$self->needsUpdating() ) {
            $action = "Kept";
            return ( $action, $err );
        }

        # exists but needs to be replaced
        $action = "Replaced";
    }
    else {
        $action = "Created";
    }
    my $content = $self->getContent();
    open my $ofh, '>', $destfn or $err = "Can't write to $destfn";
    if ( !defined $err ) {
        print $ofh $content;
        close $ofh;
    }
    return ( $action, $err );
}

####################################################################
# Utilities
sub execCommand {
    my ( $command, @args ) = @_;
    my $pid    = fork();
    my $result = 0;

    if ( $pid == 0 ) {
        exec( $command, @args );
        exit 0;
    }
    else {
        wait;
        $result = $?;
        if ( $result != 0 ) {
            print "ERROR executing: $command,  args: " . join( ' ', @args ) . "\n";
        }
    }
    return $result;
}

sub curlMe {
    my $url  = shift;
    my $curl = WWW::Curl::Easy->new;
    my $response_body;
    my $err;    # undef if no error

    $curl->setopt( CURLOPT_VERBOSE, 0 );
    if ( $url =~ m/https/ ) {
        $curl->setopt( CURLOPT_SSL_VERIFYHOST, 0 );
        $curl->setopt( CURLOPT_SSL_VERIFYPEER, 0 );
    }
    $curl->setopt( CURLOPT_IPRESOLVE,      1 );
    $curl->setopt( CURLOPT_FOLLOWLOCATION, 1 );
    $curl->setopt( CURLOPT_CONNECTTIMEOUT, 5 );
    $curl->setopt( CURLOPT_TIMEOUT,        15 );
    $curl->setopt( CURLOPT_HEADER,         0 );
    $curl->setopt( CURLOPT_FOLLOWLOCATION, 1 );
    $curl->setopt( CURLOPT_URL,            $url );
    $curl->setopt( CURLOPT_WRITEDATA,      \$response_body );
    my $retcode       = $curl->perform;
    my $response_code = $curl->getinfo(CURLINFO_HTTP_CODE);

    if ( $response_code != 200 ) {
        $err = "Got HTTP $response_code response for '$url'";
    }
    elsif ( length($response_body) == 0 ) {
        $err = "URL: $url returned empty!!";
    }
    return ( $response_body, $err );
}

{
    # cache cdn locs -- some files extracted from same downloaded archive
    my %content_for;

    sub _getDownloadContent {
        my $cdnloc = shift;
        my $err;
        if ( !exists $content_for{$cdnloc} ) {
            my $response_body;
            ( $response_body, $err ) = curlMe($cdnloc);
            $content_for{$cdnloc} = $response_body;
        }

        # could be undef indicating previous error
        return ( $content_for{$cdnloc}, $err );
    }
}

1;
